#include "PoissonRRT.h"
#include "ompl/base/goals/GoalState.h"
#include "ompl/base/goals/GoalSampleableRegion.h"
#include "ompl/datastructures/NearestNeighborsGNAT.h"
#include "ompl/tools/config/SelfConfig.h"
#include "ompl/util/Time.h"
#include <limits>
#include <iostream>
#include <fstream>
#include "PoissonSampler.h"

using namespace ompl;

namespace poisson
{

PoissonRRT::PoissonRRT(const base::SpaceInformationPtr &si) 
: base::Planner(si, "PoissonRRT")
{
    specs_.approximateSolutions = true;
    specs_.directed = true;
	specs_.multithreaded = true;

    goalBias_ = 0.05;
    maxDistance_ = 0.0;
    lastGoalMotion_ = NULL;

    Planner::declareParam<double>("range", this, &PoissonRRT::setRange, &PoissonRRT::getRange, "0.:1.:10000.");
    Planner::declareParam<double>("goal_bias", this, &PoissonRRT::setGoalBias, &PoissonRRT::getGoalBias, "0.:.05:1.");
}

PoissonRRT::~PoissonRRT(void)
{
    freeMemory();
}

void PoissonRRT::clear(void)
{
    Planner::clear();
    dynamic_cast<PoissonSampler*>(sampler_.get())->reset();
    freeMemory();
    if (nn_)
        nn_->clear();
    lastGoalMotion_ = NULL;
}

void PoissonRRT::setup(void)
{
    Planner::setup();
    tools::SelfConfig sc(si_, getName());
    sc.configurePlannerRange(maxDistance_);

    if (!nn_)
        nn_.reset(new NearestNeighborsGNAT<Motion*>());
    nn_->setDistanceFunction(boost::bind(&PoissonRRT::distanceFunction, this, _1, _2));

	if (!sampler_)
		sampler_ = si_->allocValidStateSampler();

	dynamic_cast<PoissonSampler*>(sampler_.get())->setup();
}

void PoissonRRT::freeMemory(void)
{
    if (nn_)
    {
        std::vector<Motion*> motions;
        nn_->list(motions);
        for (unsigned int i = 0 ; i < motions.size() ; ++i)
        {
            delete motions[i];
        }
    }
}

ompl::base::PlannerStatus PoissonRRT::solve(const base::PlannerTerminationCondition &ptc)
{
	double collisionCheckTime = 0;
	double samplingTime = 0;
	double nearestNodeTime = 0;
	double adpativeSamplingTime = 0;
	ompl::time::point timeStart;

    checkValidity();
    base::Goal                 *goal   = pdef_->getGoal().get();
	base::GoalState			   *goalState = dynamic_cast<base::GoalState*>(goal);

	// set start sample
	PoissonSampler* poissonSampler = dynamic_cast<PoissonSampler*>(sampler_.get());
	std::vector<double> vPos;
	while (const base::State *st = pis_.nextStart())
    {
        Motion *motion = new Motion();
		motion->state = poissonSampler->addStartPosition(st);
        nn_->add(motion);
    }
    if (nn_->size() == 0)
    {
        OMPL_ERROR("There are no valid initial states!");
        return base::PlannerStatus::INVALID_START;
    }

    OMPL_INFORM("Starting with %u states", nn_->size());

	// set goal sample
	PoissonState* poissonGoalState = poissonSampler->addGoalPosition(goalState->getState());

    Motion *solution  = NULL;
    Motion *approxsol = NULL;
    double  approxdif = std::numeric_limits<double>::infinity();
    Motion *rmotion   = new Motion();
	base::State *tstate = si_->allocState();
	base::State *tstate2 = si_->allocState();
	std::pair<base::State*,double> lastValid(tstate, 0.0);
	base::StateSamplerPtr sampler = si_->allocStateSampler();

	PoissonState poissonState(si_.get(), tstate2, -1, -1);
	RNG rng;
    while (ptc == false)
    {
		++uiNumIterations_;

        /* sample random PD-sample (with goal biasing) */
		timeStart = time::now();
        if (rng.uniform01() < goalBias_)
            rmotion->state = poissonGoalState;
        else
		{
			rmotion->state = &poissonState;
			sampler->sampleUniform(rmotion->state->getState());
			//poissonSampler->sample(rmotion->state);
		}
			
		samplingTime += time::seconds(time::now() - timeStart);

        /* find closest PD-sample in the tree */
        Motion *nmotion = nn_->nearest(rmotion);

        /* find neighbor(closest) PD-sample to add */
		timeStart = time::now();
		rmotion->state = poissonSampler->getNeighborSample(nmotion->state, rmotion->state);
		nearestNodeTime += time::seconds(time::now() - timeStart);
		if (rmotion->state == NULL)
			continue;

		timeStart = time::now();
		bool noCollision = si_->checkMotion(nmotion->state->getState(), rmotion->state->getState(), lastValid);
		collisionCheckTime += time::seconds(time::now() - timeStart);
        if (noCollision)
        {
            /* create a motion */
            Motion *motion = new Motion();
			motion->state = rmotion->state;
            motion->parent = nmotion;

			poissonSampler->stateAccepted(rmotion->state);

            nn_->add(motion);
            double dist = 0.0;
            bool sat = (motion->state == poissonGoalState);
            if (sat)
            {
                approxdif = dist;
                solution = motion;
                break;
            }
            if (dist < approxdif)
            {
                approxdif = dist;
                approxsol = motion;
            }
        }
		else
		{
			timeStart = time::now();
			poissonSampler->collisionDetected(nmotion->state, rmotion->state, lastValid.first);
			adpativeSamplingTime += time::seconds(time::now() - timeStart);
		}
    }

    bool solved = false;
    bool approximate = false;
    if (solution == NULL)
    {
        solution = approxsol;
        approximate = true;
    }

    if (solution != NULL)
    {
        lastGoalMotion_ = solution;

        /* construct the solution path */
        std::vector<Motion*> mpath;
        while (solution != NULL)
        {
            mpath.push_back(solution);
            solution = solution->parent;
        }

        /* set the solution path */
        ompl::geometric::PathGeometric *path = new ompl::geometric::PathGeometric(si_);
           for (int i = mpath.size() - 1 ; i >= 0 ; --i)
		   {
			   path->append(mpath[i]->state->getState());
		   }
        pdef_->addSolutionPath(base::PathPtr(path), approximate, approxdif);
        solved = true;
    }

    delete rmotion;

	si_->freeState(tstate);
	si_->freeState(tstate2);

    OMPL_INFORM("Created %u states", nn_->size());

	OMPL_INFORM("Time for adaptive sampling : %f", adpativeSamplingTime);
	OMPL_INFORM("Time for sampling : %f", samplingTime);
	OMPL_INFORM("Time for collision check : %f", collisionCheckTime);
	OMPL_INFORM("Time for nearest node : %f", nearestNodeTime);

	OMPL_INFORM("Created %d Poisson states", poissonSampler->getNumStates());

	poissonSampler->clearSamples();

    return base::PlannerStatus(solved, approximate);
}

void PoissonRRT::getPlannerData(base::PlannerData &data) const
{
    Planner::getPlannerData(data);

    std::vector<Motion*> motions;
    if (nn_)
        nn_->list(motions);

    if (lastGoalMotion_)
	{
        data.addGoalVertex(base::PlannerDataVertex(lastGoalMotion_->state));
	}

    for (unsigned int i = 0 ; i < motions.size() ; ++i)
    {
        if (motions[i]->parent == NULL)
		{
            data.addStartVertex(base::PlannerDataVertex(motions[i]->state));
		}
        else
		{
            data.addEdge(base::PlannerDataVertex(motions[i]->parent->state),
                         base::PlannerDataVertex(motions[i]->state));
		}
    }
}

}